import numpy as np

# The evaluation parameters are as follows (defaults in brackets):
#  imgIds     - [all] N img ids to use for evaluation
#  catIds     - [all] K cat ids to use for evaluation
#  iouThrs    - [.5:.05:.95] T=10 IoU thresholds for evaluation
#  recThrs    - [0:.01:1] R=101 recall thresholds for evaluation
#  areaRng    - [...] A=4 object area ranges for evaluation
#  maxDets    - [1 10 100] M=3 thresholds on max detections per image
#  iouType    - ['segm'] set iouType to 'segm', 'bbox' or 'keypoints'
#  iouType replaced the now DEPRECATED useSegm parameter.
#  useCats    - [1] if true use category labels for evaluation
# Note: if useCats=0 category labels are ignored as in proposal scoring.
# Note: multiple areaRngs [Ax2] and maxDets [Mx1] can be specified.
#
# evaluate(): evaluates detections on every image and every category and
# concats the results into the "evalImgs" with fields:
#  dtIds      - [1xD] id for each of the D detections (dt)
#  gtIds      - [1xG] id for each of the G ground truths (gt)
#  dtMatches  - [TxD] matching gt id at each IoU or 0
#  gtMatches  - [TxG] matching dt id at each IoU or 0
#  dtScores   - [1xD] confidence of each dt
#  gtIgnore   - [1xG] ignore flag for each gt
#  dtIgnore   - [TxD] ignore flag for each dt at each IoU
    #
class GirEvalParams:
    def __init__(self):
        # self.imgIds = []
        # self.catIds = []
        # # np.arange causes trouble.  the data point on arange is slightly larger than the true value
        # self.iouThrs = np.linspace(.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)
        # self.recThrs = np.linspace(.0, 1.00, int(np.round((1.00 - .0) / .01)) + 1, endpoint=True)
        # self.maxDets = [1, 10, 100]
        # self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
        # self.areaRngLbl = ['all', 'small', 'medium', 'large']
        # self.useCats = 1
        
        self.iouThrs = [0.5,0.75]
        self.scoreThrs = [0.5]
        self.topkThrs = [1,5,10]
        self.areaRng = [[0 ** 2, 1e5 ** 2], [0 ** 2, 32 ** 2], [32 ** 2, 96 ** 2], [96 ** 2, 1e5 ** 2]]
        self.areaRngLbl = ['all', 'small', 'medium', 'large']

        
        


class GirEval():
    def __init__(self,coco_eval):
        self.coco_eval = coco_eval
        self.params = GirEvalParams()
        self._result_str = []
        self._result_recall_per_image = None

    def evaluateImgHasTgt(self,imgId, catId, aRng, maxDet = 100):
        coco_eval = self.coco_eval
        p = coco_eval.params
        p2 = self.params
        
        if p.useCats:
            gt = coco_eval._gts[imgId,catId]
            dt = coco_eval._dts[imgId,catId]
        else:
            gt = [_ for cId in p.catIds for _ in coco_eval._gts[imgId,cId]]
            dt = [_ for cId in p.catIds for _ in coco_eval._dts[imgId,cId]]
        # if len(gt) == 0 and len(dt) ==0:
        if len(gt) == 0 or len(dt) ==0:
            return None

        for g in gt:
            if g['ignore'] or (g['area']<aRng[0] or g['area']>aRng[1]):
                g['_ignore'] = 1
            else:
                g['_ignore'] = 0
            
            bbox = g['bbox']
            it = 8.0
            if bbox[2] < it or bbox[3] < it:
                 g['_ignore'] = 1

        gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
        gt = [gt[i] for i in gtind]
        
        # dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
        # dt = [dt[i] for i in dtind[0:maxDet]]
        # ious = coco_eval.ious[imgId, catId][:, gtind] if len(coco_eval.ious[imgId, catId]) > 0 else coco_eval.ious[imgId, catId]
        
        T = len(p2.iouThrs)
        G = len(gt)
        D = len(dt)
        K = len(p2.topkThrs)
        # S = len(p2.scoreThrs)
        #T*K*G
        #用于计算召回率
        # -1表示忽略，0表示未被召回，>0表示召回，数值为对应的dt
        gtm  = np.zeros((T,K,G))
        for gg in range(G):
            gtm[:,:,gg] = -1 if gt[gg]['_ignore'] == 1 else 0
        
        #T*K*D
        #用于计算精度
        #-1表示忽略，0表示FP，>0表示TP，数值为对应的tgt
        dtm  = np.zeros((T,K,D))
        
        for iind, iv in enumerate(p2.iouThrs):
            for kind, kv in enumerate(p2.topkThrs):
                for dind, dv in enumerate(dt):
                    ###判断该dt是否是无效的
                    # if dv['score'] < 0.5 or dv['perd_ins_flag'] == False:
                    if dv['perd_ins_flag'] == False:
                        dtm[iind,kind,dind] = -1
                        continue
                    # if dtm[iind,kind,dind] != 0:
                    #     continue
                        # information about best match so far (m=-1 -> unmatched)

                    topk = np.array(dv['perd_ins_ids'][:kv])
                    tgt_insid = dv['tgt_ins_ids']
                    
                    if tgt_insid not in topk:
                        continue
                    
                    iou = min([iv,1-1e-10])
                    m   = -1
                    for gind, gv in enumerate(gt):
                        ins_id = gv['ins_id']
                        if ins_id == tgt_insid:
                            m=gind
                            iou = 0.999
                    ###该DT没有找到合适的GT,dtm=0
                    if m ==-1:
                        continue
                    
                    ###如果找到的GT是被ignore的，则该DT忽略
                    if gtm[iind,kind,m] == -1:
                        dtm[iind,kind,dind]  = -1
                    else:
                        gtm[iind,kind,m] = dv['id']##记录该GT(m)对应的DT
                        dtm[iind,kind,dind]  = gt[m]['id']##记录该GT对应的GT   
        return {
                'image_id':     imgId,
                'category_id':  catId,
                'aRng':         aRng,
                'maxDet':       maxDet,
                'dtIds':        [d['id'] for d in dt],
                'gtIds':        [g['id'] for g in gt],
                'dtMatches':    dtm,
                'gtMatches':    gtm,
                'dtScores':     [d['score'] for d in dt]
            }
        
    def evaluateImg(self,imgId, catId, aRng, maxDet = 100):
        
        coco_eval = self.coco_eval
        p = coco_eval.params
        p2 = self.params
        
        if p.useCats:
            gt = coco_eval._gts[imgId,catId]
            dt = coco_eval._dts[imgId,catId]
        else:
            gt = [_ for cId in p.catIds for _ in coco_eval._gts[imgId,cId]]
            dt = [_ for cId in p.catIds for _ in coco_eval._dts[imgId,cId]]
        # if len(gt) == 0 and len(dt) ==0:
        if len(gt) == 0 or len(dt) ==0:
            return None

        for g in gt:
            if g['ignore'] or (g['area']<aRng[0] or g['area']>aRng[1]):
                g['_ignore'] = 1
            else:
                g['_ignore'] = 0
            
            bbox = g['bbox']
            it = 512.0/448.0*16.0
            if bbox[2] < it or bbox[3] < it:
                 g['_ignore'] = 1

        gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
        gt = [gt[i] for i in gtind]
        
        dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
        dt = [dt[i] for i in dtind[0:maxDet]]
        ious = coco_eval.ious[imgId, catId][:, gtind] if len(coco_eval.ious[imgId, catId]) > 0 else coco_eval.ious[imgId, catId]
        
        T = len(p2.iouThrs)
        G = len(gt)
        D = len(dt)
        K = len(p2.topkThrs)
        # S = len(p2.scoreThrs)
        #T*K*G
        #用于计算召回率
        # -1表示忽略，0表示未被召回，>0表示召回，数值为对应的dt
        gtm  = np.zeros((T,K,G))
        for gg in range(G):
            gtm[:,:,gg] = -1 if gt[gg]['_ignore'] == 1 else 0
        
        #T*K*D
        #用于计算精度
        #-1表示忽略，0表示FP，>0表示TP，数值为对应的tgt
        dtm  = np.zeros((T,K,D))
        
        for iind, iv in enumerate(p2.iouThrs):
            for kind, kv in enumerate(p2.topkThrs):
                for dind, dv in enumerate(dt):
                    ###判断该dt是否是无效的
                    # if dv['score'] < 0.5 or dv['perd_ins_flag'] == False:
                    if dv['perd_ins_flag'] == False:
                        dtm[iind,kind,dind] = -1
                        continue
                    if dtm[iind,kind,dind] != 0:
                        continue
                        # information about best match so far (m=-1 -> unmatched)
                    iou = min([iv,1-1e-10])
                    m   = -1
                    topk = np.array(dv['perd_ins_ids'][:kv])
                    for gind, gv in enumerate(gt):
                        if gtm[iind,kind,gind] > 0:
                            continue
                        # if this gt already matched, and not a crowd, continue
                        # if m == -1 gtm[iind,kind,gind] == -1:
                        if m>-1 and gtm[iind,kind,m] == 0 and gtm[iind,kind,gind]==-1:
                            break
                                                
                        ins_id = gv['ins_id']
                        if ins_id not in topk:
                            continue
                        if ious[dind,gind] < iou:
                            continue
                        
                        iou=ious[dind,gind]
                        m=gind
                    
                    ###该DT没有找到合适的GT,dtm=0
                    if m ==-1:
                        continue
                    
                    ###如果找到的GT是被ignore的，则该DT忽略
                    if gtm[iind,kind,m] == -1:
                        dtm[iind,kind,dind]  = -1
                    else:
                        gtm[iind,kind,m] = dv['id']##记录该GT(m)对应的DT
                        dtm[iind,kind,dind]  = gt[m]['id']##记录该GT对应的GT   
                               
                    ###计算presion时，去掉得分较低的dt
                    if dv['score'] < 0.6:
                        dtm[iind,kind,dind]  = -1    
                        
        return {
                'image_id':     imgId,
                'category_id':  catId,
                'aRng':         aRng,
                'maxDet':       maxDet,
                'dtIds':        [d['id'] for d in dt],
                'gtIds':        [g['id'] for g in gt],
                'dtMatches':    dtm,
                'gtMatches':    gtm,
                'dtScores':     [d['score'] for d in dt]
            }
    def evaluate(self,has_tgt_insid = False):
        coco_eval = self.coco_eval
        p = coco_eval.params
        p2 = self.params
        
        catIds = p.catIds if p.useCats else [-1]
        topk = p2.topkThrs[-1]
        if has_tgt_insid:
            res = [self.evaluateImgHasTgt(imgId, catId, aRng)
                 for catId in catIds
                 for aRng in p2.areaRng
                 for imgId in p.imgIds
             ]
        else:
            res = [self.evaluateImg(imgId, catId, aRng)
                    for catId in catIds
                    for aRng in p2.areaRng
                    for imgId in p.imgIds
                ]
        # self.evalImgs = [x for x in res if x is not None]
        self.evalImgs = res
        print('DONE evalImgs')
        pass
    
    def accumulate(self):
        p = self.coco_eval.params
        p2 = self.params
        
        p.catIds = p.catIds if p.useCats == 1 else [-1]
        
        C           = len(p.catIds) if p.useCats else 1
        A           = len(p2.areaRng)
        T           = len(p2.iouThrs)
        K           = len(p2.topkThrs)
        
        catIds = p.catIds if p.useCats else [-1]
        setC = set(catIds)
        setA = set(map(tuple, p2.areaRng))
        setI = set(p.imgIds)
        
        setK = set(p2.topkThrs)
        
        
        c_list = [n for n, c in enumerate(p.catIds)  if c in setC]
        i_list = [n for n, i in enumerate(p.imgIds)  if i in setI]
        a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p2.areaRng)) if a in setA]
        
        I0 = len(p.imgIds)
        A0 = len(p2.areaRng)
        
        # precision   = -np.ones((C,A,K,A,M)) # -1 for the precision of absent categories
        # recall      = -np.ones((T,K,A,M))
        precision = None
        recall = None
        
        for c, c0 in enumerate(c_list):
            Nc = c0*A0*I0
            for a, a0 in enumerate(a_list):
                Na = a0*I0

                E = [self.evalImgs[Nc + Na + i] for i in i_list]
                E = [e for e in E if not e is None]
                if len(E) == 0:
                    continue
                
                dtm = np.concatenate([e['dtMatches'] for e in E], axis=2)
                gtm = np.concatenate([e['gtMatches'] for e in E], axis=2)
                D = dtm.shape[-1]
                G = gtm.shape[-1]
                if precision is None:
                    precision = -np.ones((C,A,T,K,D))
                    recall = -np.ones((C,A,T,K,G))
                precision[c,a] = dtm
                recall[c,a] = gtm
                
        self.eval = {
            'params': p,
            'counts': [C,A,T,K,D,G],
            'precision': precision,
            'recall':   recall,
        }
    def _summarize_recall_per_image(self,iouInd, areaInd, topKInd):
        p = self.coco_eval.params
        p2 = self.params
        
        p.catIds = p.catIds if p.useCats == 1 else [-1]
        
        C           = len(p.catIds) if p.useCats else 1
        A           = len(p2.areaRng)
        T           = len(p2.iouThrs)
        K           = len(p2.topkThrs)
        
        setC = set(p.catIds)
        setA = set(map(tuple, p2.areaRng))
        setI = set(p.imgIds)
        setK = set(p2.topkThrs)
        
        
        c_list = [n for n, c in enumerate(p.catIds)  if c in setC]
        i_list = [n for n, i in enumerate(p.imgIds)  if i in setI]
        a_list = [n for n, a in enumerate(map(lambda x: tuple(x), p2.areaRng)) if a in setA]
        
        I0 = len(p.imgIds)
        A0 = len(p2.areaRng)
        
        # precision   = -np.ones((C,A,K,A,M)) # -1 for the precision of absent categories
        # recall      = -np.ones((T,K,A,M))
        precision = None
        recall = None
        recall_per_image = []
        for c, c0 in enumerate(c_list):
            Nc = c0*A0*I0
            for a, a0 in enumerate(a_list):
                # if a != areaInd:
                #     continue
                Na = a0*I0

                for imgID in i_list:
                    E = self.evalImgs[Nc + Na + imgID]
                    if E is None:
                        continue
                    vals = E['gtMatches']
                    vals = vals[iouInd,topKInd,:]
                    tps = np.where(vals > 0)
                    base = np.where(vals >= 0)
                    tps = len(tps[0])
                    base = len(base[0])
                    rec = 100.0 * tps/(base + 0.000001)
                    
                    imgstr = str(E['image_id'])
                    sid = imgstr[-2]
                    
                    data = []
                    data.append(str(a))
                    data.append(sid)
                    data.append(str(tps))
                    data.append(str(base))
                    
                    recall_per_image.append(data)
                    # self.evalImgs[Nc + Na + imgID]['recall'] = rec
        return recall_per_image
        
    def _summarize_recall(self,iouInd, areaInd, topKInd ):
        p = self.coco_eval.params
        p2 = self.params
        iStr = 'Recall @ [ IoU={:0.3f} | area={:>6s} | topk={:>3d} ] = {:0.3f}'
        
        recall = self.eval['recall']
        vals = recall[0,areaInd,iouInd,topKInd]
        tps = np.where(vals > 0)
        base = np.where(vals >= 0)
        tps = len(tps[0])
        base = len(base[0])
        rec = 100.0 * tps/base
        
        rec_str = iStr.format(p2.iouThrs[iouInd], 
                          p2.areaRngLbl[areaInd],
                          p2.topkThrs[topKInd],
                          rec)
        # print(rec_str)
        return rec_str
    
    def _summarize_precision(self,iouInd, areaInd, topKInd ):
        p = self.coco_eval.params
        p2 = self.params
        iStr = 'Precision @ [ IoU={:0.3f} | area={:>6s} | topk={:>3d} ] = {:0.3f}'
        
        precision = self.eval['precision']
        vals = precision[0,areaInd,iouInd,topKInd]
        tps = np.where(vals > 0)
        base = np.where(vals >= 0)
        tps = len(tps[0])
        base = len(base[0])
        rec = 100.0 * tps/base
        
        rec_str = iStr.format(p2.iouThrs[iouInd], 
                          p2.areaRngLbl[areaInd],
                          p2.topkThrs[topKInd],
                          rec)
        # print(rec_str)
        return rec_str
    def summarize(self):
        '''
        R@(1,0.75)
        R@(5,0.75)
        R@(10,0.5)
        
        acc@(1,0.75)
        acc@(5,0.75)
        acc@(10,0.5)
        '''
        
        self._result_str = []
        
        p = self.coco_eval.params
        p2 = self.params
        for iou in range(len(p2.iouThrs)):
            for area in range(len(p2.areaRng)):
                for topk in range(len(p2.topkThrs)):
                    res_str = self._summarize_recall(iou,area,topk)#0.75,1
                    self._result_str.append(res_str)
        # self._summarize_recall(1,0,1)
        # self._summarize_recall(0,0,2)
        for iou in range(len(p2.iouThrs)):
            for area in range(len(p2.areaRng)):
                for topk in range(len(p2.topkThrs)):
                    res_str = self._summarize_precision(iou,area,topk)#0.75,1
                    self._result_str.append(res_str)
        self._result_recall_per_image = None
        # self._result_recall_per_image = self._summarize_recall_per_image(0,0,2)
        
        # self._summarize_precision(1,0,0)#0.75,1
        # self._summarize_precision(1,0,1)
        # self._summarize_precision(0,0,2)